Repository navigation
fix(screenshot): add the capture origin to the image-to-screen conversion - #613
Merged
Jeomon merged 1 commit intoNov 12, 2026
Merged
Conversation
…sion Image pixel (0, 0) is the top-left corner of what was captured: the region, the selected displays, or the virtual screen, which starts left of or above (0, 0) when a monitor sits left of or above the primary one. The Screenshot Coordinate Scale note told the agent to multiply image pixels by the scale and nothing else, so for those captures a point worked out from the image missed by that corner's position, often onto another monitor (CursorTouch#453). A capture at full size that did not start at (0, 0) came with no conversion at all. The note now adds the corner when it is not (0, 0), in its instruction and in its worked example, and a full-size capture that starts elsewhere gets a Screenshot Origin line. Captures that start at (0, 0) read as before. The Screenshot description now says to add the corner too.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Screenshot lists no UI elements, so an agent that works from it takes its click targets from the image, and the response tells it how to turn an image pixel into a screen coordinate. When the image is downscaled, the response says (c46911b, for #82 and #99):
That is right only when image pixel (0, 0) is screen coordinate (0, 0). The image starts at the top-left corner of what was captured:
region=[left, top, right, bottom]: at(left, top).display=[...]: at the corner of the selected displays,(1920, 0)for a monitor right of a 1920x1080 primary.ImageGrab.grab(all_screens=True)returns the whole virtual screen, andget_annotated_screenshot()already subtracts that corner (uia.GetVirtualScreenRect()) when it draws the element boxes.For these captures, a point worked out from the image the way the note says misses by the corner's position: with a 1920-pixel-wide monitor left of the primary, a click aimed at that monitor lands 1920 px to the right, on the primary. Snapshot with
use_vision=Trueprints the same note. A capture that is not downscaled, such as a small region ordisplay=[1]on a 1920x1080 monitor, gets no conversion at all: the response showsScreenshot RegionandCoordinate Space: Virtual desktop coordinates, but an image pixel is not a virtual desktop coordinate there.This is the second item of #453 ("Screenshot coordinate guidance omits the crop origin"). #453 was closed after #473 fixed its Scrape item, and its first item was fixed later by #559. #471 covered this item along with others; its author closed it without it being merged, and the note is unchanged on
main.Reproduction
At 3416c76 on Windows 11 (build 22631) with Python 3.14.9, with mocks only; nothing was captured from the real screen. The
Desktopcame fromDesktop.__new__. Its displays, cursor andget_screenshot()were stand-ins, anduia.GetVirtualScreenRect,get_current_desktopandget_all_desktopswere patched. The stand-in capture cut its image out of a synthetic virtual desktop with a 9x9 red "button" at a known screen point, the way the Pillow backend cuts a region out of the virtual screen. The script rancapture_desktop_state()andbuild_snapshot_response()with the Screenshot tool's arguments, found the button in the returned PNG, and mapped the button's pixel to the screen the way the response's worked example maps (200, 150). Output onmain:The README suggests
WINDOWS_MCP_SCREENSHOT_SCALE("0.5"in its example) on 1440p and 4K displays; with it, every region and display capture is downscaled and gets the note.Fix
tools/_snapshot_helpers.py: a new_image_origin()returns the screen coordinate of the image's top-left pixel: the corner ofscreenshot_regionfor a region or display capture, else the top-left corner of the displays the response lists underVisible Displays, which is the corner of the virtual screen.multiply every image pixel coordinate by 2.0, then add (-1920,0), the screen coordinate of image pixel (0, 0), before passing to Click, Move, Scroll, or any loc= argument (e.g. image pixel (200, 150) → screen coordinate (-1520, 300)).Screenshot Origin: (100,100) — image pixel (0, 0) is screen coordinate (100,100); add (100,100) to every image pixel coordinate before passing to Click, Move, Scroll, or any loc= argument (e.g. image pixel (200, 150) → screen coordinate (300, 250)).tools/snapshot.py, and the same sentence inmanifest.json) said to multiply by the ratio of original size to displayed size "to get the actual screen coordinates". It now says that the image starts at the top-left corner of the captured area, not necessarily at screen (0, 0), and to add the screen coordinate of image pixel (0, 0) when the response gives one.With the change, the reproduction prints the same output except for these lines, which change or are added:
The single-monitor 4K capture prints exactly what it printed on
main.I took the full capture's corner from the displays already in
DesktopStateto keep the change in one module. If you would rather haveget_state()record the corner it captured (capture_rect, elseuia.GetVirtualScreenRect(), asget_annotated_screenshot()does), I can move it there.Not changed: the note still gives one scale for both axes. #453 also asked for separate X and Y ratios; the truncation in the resize makes them differ from that scale by less than one image pixel across the whole image.
Tests
New
tests/test_screenshot_coordinate_origin.py, mocks only. A helper builds aDesktopwith__init__patched out, as intests/test_snapshot_display_filter.py. Its displays, cursor andget_screenshot()are stand-ins, anduia.GetVirtualScreenRect,get_current_desktopandget_all_desktopsare patched. The capture is cut out of a synthetic virtual desktop with a red button at a known screen point. The helper runscapture_desktop_state()andbuild_snapshot_response()with the Screenshot tool's arguments and decodes the returned PNG. An autouse fixture clearsWINDOWS_MCP_SCREENSHOT_SCALE.test_image_pixel_maps_to_its_screen_coordinate: finds the button in the returned image, maps its pixel to the screen the way the response's worked example does, and checks that the result is within 2 px of the button. Cases: a full capture with a monitor left of the primary, a full capture with a monitor above it,display=[1]for a 2560x1440 monitor right of the primary, a region withWINDOWS_MCP_SCREENSHOT_SCALE=0.5, and a region at full size. (5 cases)test_downscaled_capture_at_screen_origin_keeps_its_note: a full capture of a single 3840x2160 monitor; the note is word for word the one onmain.test_full_size_capture_at_screen_origin_needs_no_conversion: a full capture of a single 1920x1080 monitor; the response hasScreenshot Size: (1920,1080)and no conversion.I ran each file on its own, in the project's environment:
On
main, 5 of the 7 new tests fail. Four fail because the mapped point misses the button:the response maps the button's image pixel to (420, 300), but the button is at (-1500, 300),(700, 680)instead of(700, -400),(580, 599)instead of(2500, 600), and(400, 300)instead of(2400, 1300). The region at full size fails withthe response says nothing about turning image pixels into screen coordinates. The two captures that start at (0, 0) pass on both. All 7 pass on this branch.These pass on
mainand on this branch with the same counts:tests/test_snapshot_display_filter.py(37 passed),tests/test_screenshot_grid_lines.py(9),tests/test_screenshot_window_list.py(2),tests/test_manifest_tools.py(1) andtests/test_check_versions.py(14). I did not run the whole suite.ruff checkis clean on the three changed Python files.ruff format --checkis clean on the new test file._snapshot_helpers.pyandsnapshot.pyare not formatted onmaineither (quote style: thededent()f-strings andname='Snapshot'); this change adds no hunk to theirruff format --diff.Not tested: a real multi-monitor desktop. Everything ran against stand-ins, and I read the image's starting corner off the capture code (
ImageGrab.grab(all_screens=True)and the region/display crops) rather than measuring it on real monitors. To check it on a real machine, place a second monitor left of the main one in Display settings, call Screenshot, pick a point on the left monitor in the image (a window's title text, say), convert it as the response says, and call Move with the result. With this change the pointer should land on that point; onmainit lands on the main monitor, one left-monitor width to the right. One case I left alone: if Pillow cannot grab all screens, a full capture falls back to the primary monitor alone (the log says "Failed to capture virtual screen, using primary screen"). That image starts at (0, 0), butget_state()is not told about the fallback, so on a desktop whose virtual screen starts elsewhere the note adds the virtual screen's corner.